﻿using DotNetCommon;
using DotNetCommon.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Test.MySql
{
    /// <summary>
    /// 测试Id和流水号生成,基于数据库缓存
    /// </summary>
    [TestClass]
    public sealed partial class GeneratorTests : TestBase
    {
        private void ClearDBCache()
        {
            db.Manage.DropTableIfExist(db.Settings.DBCacheGeneratorIdTableName);
            db.Manage.DropTableIfExist(db.Settings.DBCacheGeneratorSNOTableName);
            db.Manage.DropTableIfExist(db.Settings.DBCacheGeneratorLogTableName);
            db.Manage.DropProcedureIfExist(db.Settings.DBCacheGeneratorIdProcedureName);
            db.Manage.DropProcedureIfExist(db.Settings.DBCacheGeneratorSNOProcedureName);
        }

        #region Id生成中的各种情况测试
        /// <summary>
        /// 当表不存在报错时
        /// </summary>
        [TestMethod]
        public void TestNoTableError()
        {
            ClearDBCache();
            db.Manage.DropTableIfExist("test5");
            try
            {
                var id = db.NewId("test5", "id");
                throw new Exception("竟然没报错!");
            }
            catch (Exception ex)
            {
                int i = 0;
            }
        }

        /// <summary>
        /// 当表中还没有数据时
        /// </summary>
        [TestMethod]
        public void TestTableHasNoData()
        {
            ClearDBCache();
            db.Manage.DropTableIfExist("test");
            db.ExecuteSql("create table test(id int primary key,name varchar(50))");
            var id = db.NewId("test", "id");
            Assert.IsTrue(id == 1);
            id = db.NewId("test", "id");
            Assert.IsTrue(id == 2);
        }

        /// <summary>
        /// 当表中已有数据时
        /// </summary>
        [TestMethod]
        public void TestTableHasData()
        {
            ClearDBCache();
            db.Manage.DropTableIfExist("test");
            db.ExecuteSql("create table test(id int primary key,name varchar(50))");
            db.Insert("test", new { id = 1, name = "小明" }.ToDictionary());
            var id = db.NewId("test", "id");
            Assert.IsTrue(id == 2);
            id = db.NewId("test", "id");
            Assert.IsTrue(id == 3);
        }

        /// <summary>
        /// 当表中已有缓存时
        /// </summary>
        [TestMethod]
        public void TestHasDataCache()
        {
            ClearDBCache();
            db.ExecuteSql(@"
CREATE TABLE `__generator_id` (
  `tablename` varchar(50) NOT NULL,
  `colname` varchar(50) NOT NULL,
  `currentid` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`tablename`,`colname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
            db.Insert("__generator_id", new { tablename = "test", colname = "id", currentid = 5 }.ToDictionary());
            var id = db.NewId("test", "id");
            Assert.IsTrue(id == 6);
            id = db.NewId("test", "id");
            Assert.IsTrue(id == 7);
        }

        /// <summary>
        /// 测试未获取到锁超时
        /// </summary>
        [TestMethod]
        public void TestErrorTimeout()
        {
            ClearDBCache();
            db.Manage.DropTableIfExist("test");
            db.ExecuteSql("create table test(id int primary key,name varchar(50))");
            var id = db.NewId("test", "id");
            Assert.IsTrue(id == 1);

            //制造一个锁 30秒
            var task1 = Task.Factory.StartNew(() =>
              {
                  try
                  {
                      var newDb = db.CreateNewDB();
                      using (newDb.OpenLongConnectOnce())
                      {
                          newDb.ExecuteSql($"SELECT GET_LOCK('{db.DataBase}_test_id',10);");
                          Thread.Sleep(30 * 1000);
                          newDb.ExecuteSql($"SELECT RELEASE_LOCK('{db.DataBase}_test_id');");
                      }
                  }
                  catch (Exception ex)
                  {
                      //不应该报错
                      throw ex;
                  }
              });
            var task2 = Task.Factory.StartNew(() =>
            {
                try
                {
                    Thread.Sleep(1000);
                    var id = db.NewId("test", "id");
                    throw new Exception("应该报超时的错!");
                }
                catch (Exception ex)
                {
                    //锁超时信息
                    Assert.IsTrue(ex.Message.Contains("20秒内"));
                }
            });
            Task.WaitAll(task1, task2);
        }
        #endregion

        #region Id生成
        [TestCategory("Id生成-基于数据库缓存")]
        [TestMethod]
        public void NewId_DBCacheTest()
        {
            ClearDBCache();
            using (db.OpenLongConnectOnce())
            {
                //准备测试表
                db.Manage.DropTableIfExist("test");
                db.ExecuteSql(@"create table test(
id int primary key,
name varchar(50)
)");
                //单个id生成
                long id = db.NewId("test", "id");
                Assert.IsTrue(id == 1);
                id = db.NewId("test", "id");
                Assert.IsTrue(id == 2);
                //根据表中已有的id,继续生成
                var dic = new Dictionary<string, object>();
                dic.Add("id", id);
                dic.Add("name", "xiaoming" + id);
                db.Insert("test", dic);
                db.ResetId("test", "id");
                id = db.NewId("test", "id");
                Assert.IsTrue(id == 3);
                //根据缓存批量生成id
                var ids = db.NewIds("test", "id", 5);
                var expect = new List<long>() { 4, 5, 6, 7, 8 };
                var expectStr = string.Join(",", expect);
                var actStr = string.Join(",", ids);
                Assert.IsTrue(expectStr == actStr);
                //根据表中已有的id,继续批量生成
                db.ResetId("test", "id");
                ids = db.NewIds("test", "id", 5);
                expect = new List<long>() { 3, 4, 5, 6, 7 };
                expectStr = string.Join(",", expect);
                actStr = string.Join(",", ids);
                Assert.IsTrue(expectStr == actStr);
                //直接从缓存中生成
                db.Manage.TruncateTable("test", null);
                db.ResetId("test", "id");
                ids = db.NewIds("test", "id", 5);
                expect = new List<long>() { 1, 2, 3, 4, 5 };
                expectStr = string.Join(",", expect);
                actStr = string.Join(",", ids);
                Assert.IsTrue(expectStr == actStr);
            };
        }

        [TestCategory("Id生成-基于数据库缓存-并发测试")]
        [TestMethod]
        public void NewIdConcurrency_DBCacheTest()
        {
            ClearDBCache();
            //准备测试表
            db.Manage.DropTableIfExist("test");
            db.ExecuteSql(@"create table test(
id int primary key,
name varchar(50)
)");
            db.Manage.TruncateTable("test", null);
            var tasks = new List<Task>();
            var idres = new System.Collections.Concurrent.ConcurrentQueue<(long id, int threadid)>();
            for (int i = 0; i < 100; i++)
            {
                tasks.Add(Task.Factory.StartNew(() =>
                {
                    try
                    {
                        var db2 = db.CreateNewDB();
                        using (db2.OpenLongConnectOnce())
                        {
                            for (int j = 0; j < 1; j++)
                            {
                                var idg = db2.NewId("test", "id");
                                idres.Enqueue((idg, Thread.CurrentThread.ManagedThreadId));
                            }
                        };
                    }
                    catch (Exception ex)
                    {
                        Debug.Write(ex?.Message + ex?.StackTrace);
                        Debug.Fail(ex?.Message, ex?.StackTrace);
                    }
                }, TaskCreationOptions.LongRunning));
            }
            Task.WaitAll(tasks.ToArray());
            var list = idres.ToList();
            list = list.OrderByDescending(t => t.id).ToList();
            var expactCount = 100 * 1;
            Assert.IsTrue(list.LastOrDefault().id == 1);
            Assert.IsTrue(list.FirstOrDefault().id == expactCount);
            Assert.IsTrue(list.Count == expactCount);
        }
        #endregion

        #region SNO生成中的各种情况测试
        /// <summary>
        /// 表不存在,报错
        /// </summary>
        [TestMethod]
        public void TestErrorSql()
        {
            ClearDBCache();
            db.Manage.DropTableIfExist("test");
            try
            {
                var sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
                throw new Exception("竟然没报错!");
            }
            catch (Exception ex)
            {
                int i = 0;
            }
        }

        /// <summary>
        /// 无缓存 表中没有匹配数据
        /// </summary>
        [TestMethod]
        public void TestNoCacheNoData()
        {
            ClearDBCache();
            db.Manage.DropTableIfExist("test");
            db.ExecuteSql("create table test(sno varchar(50))");
            var sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
            Assert.IsTrue(sno == $"SNO{DateTime.Now.ToString("yyyyMMdd")}000001");
            sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
            Assert.IsTrue(sno == $"SNO{DateTime.Now.ToString("yyyyMMdd")}000002");
        }

        /// <summary>
        /// 无缓存 表中有匹配数据
        /// </summary>
        [TestMethod]
        public void TestNoCacheHasData()
        {
            ClearDBCache();
            db.Manage.DropTableIfExist("test");
            db.ExecuteSql("create table test(sno varchar(50))");
            db.Insert("test", new { sno = $"SNO{DateTime.Now.ToString("yyyyMMdd")}000005" }.ToDictionary());
            var sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
            Assert.IsTrue(sno == $"SNO{DateTime.Now.ToString("yyyyMMdd")}000006");
            sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
            Assert.IsTrue(sno == $"SNO{DateTime.Now.ToString("yyyyMMdd")}000007");
        }

        /// <summary>
        /// 有缓存,时间戳对不上
        /// </summary>
        [TestMethod]
        public void TestHasCacheWrongTimeStamp()
        {
            ClearDBCache();
            db.Manage.DropTableIfExist("test");
            db.ExecuteSql(@"
create table __generator_sno
(
	tablename varchar(50),
	colname varchar(50),
	statictext varchar(200),
	machineidstr varchar(50),
	nowstr varchar(100),
	currentno bigint,
	primary key(tablename,colname,statictext)
)");
            db.Insert("__generator_sno", new { tablename = "test", colname = "sno", statictext = $"SNO", nowstr = DateTime.Now.AddDays(-1).ToString("yyyyMMdd"), currentno = "5" }.ToDictionary());
            var sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));

            Assert.IsTrue(sno == $"SNO{DateTime.Now.ToString("yyyyMMdd")}000001");
            sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
            Assert.IsTrue(sno == $"SNO{DateTime.Now.ToString("yyyyMMdd")}000002");
        }

        /// <summary>
        /// 有缓存,时间戳对得上
        /// </summary>
        [TestMethod]
        public void TestHasCacheRightTimeStamp()
        {
            ClearDBCache();
            db.Manage.DropTableIfExist("test");
            db.ExecuteSql(@"
create table __generator_sno
(
	tablename varchar(50),
	colname varchar(50),
	statictext varchar(200),
	machineidstr varchar(50),
	nowstr varchar(100),
	currentno bigint,
	primary key(tablename,colname,statictext)
)");
            db.Insert("__generator_sno", new { tablename = "test", colname = "sno", statictext = $"SNO", nowstr = DateTime.Now.ToString("yyyyMMdd"), currentno = "2" }.ToDictionary());
            var sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));

            Assert.IsTrue(sno == $"SNO{DateTime.Now.ToString("yyyyMMdd")}000003");
            sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
            Assert.IsTrue(sno == $"SNO{DateTime.Now.ToString("yyyyMMdd")}000004");
        }

        [TestMethod]
        public void TestLockTimeout()
        {
            ClearDBCache();
            db.Manage.DropTableIfExist("test");
            db.ExecuteSql("create table test(sno varchar(50))");
            var sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
            Assert.IsTrue(sno == $"SNO{DateTime.Now.ToString("yyyyMMdd")}000001");

            //制造一个锁 30秒
            var task1 = Task.Factory.StartNew(() =>
            {
                try
                {
                    var newDb = db.CreateNewDB();
                    using (newDb.OpenLongConnectOnce())
                    {
                        newDb.ExecuteSql($"SELECT GET_LOCK('{db.DataBase}_test_sno_SNO',10);");
                        Thread.Sleep(30 * 1000);
                        newDb.ExecuteSql($"SELECT RELEASE_LOCK('{db.DataBase}_test_sno_SNO');");
                    }
                }
                catch (Exception ex)
                {
                    //不应该报错
                    throw ex;
                }
            });
            var task2 = Task.Factory.StartNew(() =>
            {
                try
                {
                    Thread.Sleep(1000);
                    var id = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
                    throw new Exception("应该报超时的错!");
                }
                catch (Exception ex)
                {
                    //锁超时信息
                    Assert.IsTrue(ex.Message.Contains("20秒内"));
                }
            });
            Task.WaitAll(task1, task2);
        }
        #endregion

        #region SNO生成
        [TestCategory("测试SNO生成-基于数据库缓存")]
        [TestMethod]
        public void NewSNO_DBCacheTest()
        {
            ClearDBCache();
            using (db.OpenLongConnectOnce())
            {
                //准备测试表
                db.Manage.DropTableIfExist("test");
                db.ExecuteSql(@"create table test(
id int primary key,
sno varchar(50),
name varchar(50)
)");
                //单个生成
                var sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
                Assert.IsTrue(sno == "SNO" + DateTime.Now.ToString("yyyyMMdd") + "000001");
                sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
                Assert.IsTrue(sno == "SNO" + DateTime.Now.ToString("yyyyMMdd") + "000002");
                //根据表中已有的sno,继续生成
                var dic = new Dictionary<string, object>();
                var id = db.NewId("test", "id");
                dic.Add("id", id);
                dic.Add("name", "xiaoming" + id);
                dic.Add("sno", sno);
                db.Insert("test", dic);
                db.ResetSNO("test", "sno");
                sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
                Assert.IsTrue(sno == "SNO" + DateTime.Now.ToString("yyyyMMdd") + "000003");
                var dt3 = db.SelectDataTable($"select * from test");
                //根据缓存批量生成sno
                var snos = db.NewSNOs("test", "sno", SerialFormat.CreateFast("SNO"), 5);
                var expect = new List<string>() { $"SNO{DateTime.Now.ToString("yyyyMMdd")}000004", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000005", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000006", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000007", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000008" };
                var expectStr = string.Join(",", expect);
                var actStr = string.Join(",", snos);
                Assert.IsTrue(expectStr == actStr);
                //根据表中已有的id,继续批量生成
                db.ResetSNO("test", "sno");
                snos = db.NewSNOs("test", "sno", SerialFormat.CreateFast("SNO"), 5);
                expect = new List<string>() { $"SNO{DateTime.Now.ToString("yyyyMMdd")}000003", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000004", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000005", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000006", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000007" };
                expectStr = string.Join(",", expect);
                actStr = string.Join(",", snos);
                Assert.IsTrue(expectStr == actStr);
                //直接从缓存中生成
                db.Manage.TruncateTable("test", null);
                db.ResetSNO("test", "sno");
                var dt = db.SelectDataTable($"select * from {db.Settings.DBCacheGeneratorSNOTableName}");
                var dt2 = db.SelectDataTable($"select * from test");
                sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
                Assert.IsTrue(sno == "SNO" + DateTime.Now.ToString("yyyyMMdd") + "000001");
                snos = db.NewSNOs("test", "sno", SerialFormat.CreateFast("SNO"), 5);
                expect = new List<string>() { $"SNO{DateTime.Now.ToString("yyyyMMdd")}000002", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000003", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000004", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000005", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000006" };
                expectStr = string.Join(",", expect);
                actStr = string.Join(",", snos);
                Assert.IsTrue(expectStr == actStr);
            };
        }

        [TestCategory("测试SNO生成-基于数据库缓存-并发测试")]
        [TestMethod]
        public void NewSNO_DBCacheConcurrencyTest()
        {
            ClearDBCache();
            //准备测试表
            db.Manage.DropTableIfExist("test");
            db.ExecuteSql(@"create table test(
id int primary key,
sno varchar(50),
name varchar(50) 
)");
            db.Manage.TruncateTable("test", "test");
            var tasks = new List<Task>();
            var snores = new System.Collections.Concurrent.ConcurrentQueue<(string sno, int threadid)>();
            for (int i = 0; i < 100; i++)
            {
                tasks.Add(Task.Factory.StartNew(() =>
                {
                    try
                    {
                        var db2 = db.CreateNewDB();
                        using (db2.OpenLongConnectOnce())
                        {
                            for (int j = 0; j < 1; j++)
                            {
                                var sno = db2.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
                                snores.Enqueue((sno, Thread.CurrentThread.ManagedThreadId));
                            }
                        };
                    }
                    catch (Exception ex)
                    {
                        Debug.Write(ex?.Message + ex?.StackTrace);
                        Debug.Fail(ex?.Message, ex?.StackTrace);
                    }
                }, TaskCreationOptions.LongRunning));
            }
            Task.WaitAll(tasks.ToArray());
            var list = snores.ToList();
            list = list.OrderByDescending(t => t.sno).ToList();
            var expactCount = 100 * 1;
            Assert.IsTrue(list.FirstOrDefault().sno == $"SNO{DateTime.Now.ToString("yyyyMMdd")}{expactCount.ToString().PadLeft(6, '0')}");
            Assert.IsTrue(list.Count == expactCount);
        }
        #endregion
    }
}
